﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using Bravura.Contracts.PluginTypes;
using Bravura.Core.PluginLib;
using Bravura.Native.PluginBase;

namespace Bravura.Native.Plugins.Filters
{
	/// <summary>
	/// A delay with internal feedback.
	/// </summary>
	public class Delay : Filter, IFilter
	{
		#region Fields

		private double _secsPerBeat = 0.5;
		private double _delayInBeats = 0.75;
		private double _delayInSecs = 0.00;
		private double _delayTime = 0.50;
		private double _delaySamps = 0.0;
		private double _feedback = 0.50;
		private int _delayBufferSize = 0;
		private double[] _delayBuffer = null;
		private int _inPos = 0;
		private double _outPos = 0.0;

		#endregion

		#region Properties

		/// <summary>
		/// The delay amount in beats.
		/// </summary>
		public double DelayInBeats
		{
			get { return _delayInBeats; }
			set { _delayInBeats = value; recalcDelayTime(); }
		}

		/// <summary>
		/// The delay amount in seconds.
		/// </summary>
		public double DelayInSeconds
		{
			get { return _delayInSecs; }
			set { _delayInSecs= value; recalcDelayTime(); }
		}

		/// <summary>
		/// The feedback amount.
		/// </summary>
		public double Feedback
		{
			get { return _feedback; }
			set { _feedback = value; }
		}

		#endregion

		public Delay(IAudioPluginHost host, int sampleRate) : base(host, sampleRate)
		{
			_secsPerBeat = 60.0 / host.GetTempo();
		}

		#region Open / Process / Close

		public override void Open()
		{
			_delaySamps = _sampleRate * _delayTime;
			_delayBufferSize = Math.Max((int)(1.25 * _delaySamps), 8192);
			_delayBuffer = new double[_delayBufferSize];

			// set arbitrary input position
			_inPos = _delayBufferSize / 2;

			// adjust output position
			updateOutPos();
		}

		public override void Process(int count)
		{
			// read input buffer
			double[] ioBuff = GetInputBuffer(0).Read(count);

			// process
			for (int i = 0; i < count; i++)
			{
				// calc output sample
				double outSamp = _feedback * readBufferVal(i);

				// calc input sample
				double inSamp = ioBuff[i] + outSamp;

				// set output sample
				ioBuff[i] = _wetMix * outSamp + _dryMix * ioBuff[i];

				// set input sample
				int targetPos = _inPos + i;
				if (targetPos >= _delayBufferSize)
				{
					targetPos = targetPos % _delayBufferSize;
				}
				_delayBuffer[targetPos] = inSamp;
			}

			// adjust input position
			_inPos = (_inPos + count) % _delayBufferSize;

			// adjust output position
			updateOutPos();

			// write to output buffer
			GetOutputBuffer(0).Write(ioBuff, count);
		}

		public override void Close()
		{
			//
		}

		#endregion

		#region Private

		private void recalcDelayTime()
		{
			_delayTime = (_secsPerBeat * _delayInBeats) + _delayInSecs;
		}

		private void updateOutPos()
		{
			_outPos = _inPos - _delaySamps;
			while (_outPos < 0.0)
			{
				_outPos += _delayBufferSize;
			}
		}

		private double readBufferVal(int i)
		{
			double realPos = _outPos + i;

			int intPos0 = (int)realPos;
			int intPos1 = intPos0 + 1;

			double mix = realPos - intPos0;

			while (intPos0 >= _delayBufferSize) intPos0 -= _delayBufferSize;
			while (intPos1 >= _delayBufferSize) intPos1 -= _delayBufferSize;

			double val0 = _delayBuffer[intPos0];
			double val1 = _delayBuffer[intPos1];

			return mix * val1 + (1.0 - mix) * val0;
		}

		#endregion
	}
}
